/************************************************************************
<4loco-aprs.  This program connects to a specified APRS server and displays
    the information on a map. It's being developed using QT Creator. The project
    began in February and version 1 should be released in April. For more
    information go to http://code.google.com/p/4loco-aprs/>

    Copyright (C) <2011>  <Kyle Roden, Glen Riden, Aaron Smith, Alisa Emerich>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
**************************************************************************/

/****************************************************************************
**
** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
** Contact: Qt Software Information (qt-info@nokia.com)
**
** This file is part of the Graphics Dojo project on Qt Labs.
**
** This file may be used under the terms of the GNU General Public
** License version 2.0 or 3.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of
** this file.  Please review the following information to ensure GNU
** General Public Licensing requirements will be met:
** http://www.fsf.org/licensing/licenses/info/GPLv2.html and
** http://www.gnu.org/copyleft/gpl.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at qt-sales@nokia.com.
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
****************************************************************************/
#include <QtCore>
#include <QtGui>
#include <QtNetwork>
#include <math.h>
#include <QDir>
#include <QFile>
#include "dockwidget.h"
#include "connectstatus.h"
#include "timingsettings.h"
#include "connectionmanager.h"
#include "configstation.h"
#include "aprsparser.h"
#include "conectionthread.h"
#include "purgethread.h"
#include <QtDebug>

#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
//this is a test comment



uint qHash(const QPoint& p){

    return p.x() * 17 ^ p.y();
}

// tile size in pixels
const int tdim = 256;

//int zoomVar=7;

//convert coordinates to tile number
QPointF tileForCoordinate(qreal lat, qreal lng, int zoom){

    qreal zn = static_cast<qreal>(1 << zoom);
    qreal tx = (lng + 180.0) / 360.0;
    qreal ty = (1.0 - log(tan(lat * M_PI / 180.0) + 1.0 / cos(lat * M_PI / 180.0)) / M_PI) / 2.0;
    return QPointF(tx * zn, ty * zn);
}

//convert tile number to longitude
qreal longitudeFromTile(qreal tx, int zoom){

    qreal zn = static_cast<qreal>(1 << zoom);
    qreal lat = tx / zn * 360.0 - 180.0;
    return lat;
}

//convert tile number to latitude
qreal latitudeFromTile(qreal ty, int zoom)
{
    qreal zn = static_cast<qreal>(1 << zoom);
    qreal n = M_PI - 2 * M_PI * ty / zn;
    qreal lng = 180.0 / M_PI * atan(0.5 * (exp(n) - exp(-n)));
    return lng;
}

//recreates database table after you use drop
void populatePacketsDatabase(QSqlDatabase &packetsDatabase)
{

    QSqlQuery popQuery(packetsDatabase);
    if(!(popQuery.exec("CREATE TABLE packets ("
                       "visual_timestamp varchar(50), "
                       "raw_timestamp INT, "
                       "handled INT, "
                       "symbol_code char, "
                       "source_call_sign varchar(10), "
                       "destination_call_sign varchar(10), "
                       "latitude REAL, "
                       "longitude REAL, "
                       "altitude REAL,"
                       "speed REAL,"
                       "course INT,"
                       "posresolution DOUBLE,"
                       "temperature FLOAT,"
                       "pressure FLOAT,"
                       "wind_speed FLOAT,"
                       "wind_direction INTEGER,"
                       "wind_gust FLOAT,"
                       "rain_1h FLOAT,"
                       "rain_24h FLOAT,"
                       "rain_midnight FLOAT,"
                       "snow_24h FLOAT,"
                       "raincount INTEGER,"
                       "humidity INTEGER,"
                       "luminosity INTEGER,"
                       "data blob, "
                       "comment blob, "
                       "raw_packet blob, "
                       "packet_header blob, "
                       "packet_body blob"
                       ");")))
        qDebug() << popQuery.lastError();



}

//wipes database (obviously)
/* void wipeDatabase(QSqlDatabase &db)
{
    QSqlQuery wipeQuery(db);
    wipeQuery.exec("DROP TABLE packets;");
    populatePacketsDatabase(db);
    qDebug() << "\"packets\" table has been dropped and recreated.";
}
*/

//structure for packets displayed on map
struct activePacket{
    float latitude;
    float longitude;
    QString source;
    QString dest;
    QDateTime timestamp;
    QString message;
    QColor lineColor;
    QRectF symbolGridPos;
    QPointF symbolPos;
    QString data;
    QString plotName;
    enum aprstype type;

};

QRectF getSymbol(const char symbol){


    switch (symbol){
    case '!' : return(QRectF(1,1,20,20));
    case '"' : return(QRectF(22,1,20,20));
    case '#' : return(QRectF(43,1,20,20));
    case '$' : return(QRectF(64,1,20,20));
    case '%' : return(QRectF(85,1,20,20));
    case '&' : return(QRectF(106,1,20,20));
    case '\'' : return(QRectF(127,1,20,20));
    case '(' : return(QRectF(148,1,20,20));
    case ')' : return(QRectF(169,1,20,20));
    case '*' : return(QRectF(190,1,20,20));
    case '+' : return(QRectF(211,1,20,20));
    case ',' : return(QRectF(232,1,20,20));
    case '-' : return(QRectF(253,1,20,20));
    case '.' : return(QRectF(274,1,20,20));
    case '/' : return(QRectF(295,1,20,20));
    case '0' : return(QRectF(316,1,20,20));
    case '1' : return(QRectF(1,22,20,20));
    case '2' : return(QRectF(22,22,20,20));
    case '3' : return(QRectF(43,22,20,20));
    case '4' : return(QRectF(64,22,20,20));
    case '5' : return(QRectF(85,22,20,20));
    case '6' : return(QRectF(106,22,20,20));
    case '7' : return(QRectF(127,22,20,20));
    case '8' : return(QRectF(148,22,20,20));
    case '9' : return(QRectF(169,22,20,20));
    case ':' : return(QRectF(190,22,20,20));
    case ';' : return(QRectF(211,22,20,20));
    case '<' : return(QRectF(232,22,20,20));
    case '=' : return(QRectF(253,22,20,20));
    case '>' : return(QRectF(274,22,20,20));
    case '?' : return(QRectF(295,22,20,20));
    case '@' : return(QRectF(316,22,20,20));
    case 'A' : return(QRectF(1,43,20,20));
    case 'B' : return(QRectF(22,43,20,20));
    case 'C' : return(QRectF(43,43,20,20));
    case 'D' : return(QRectF(64,43,20,20));
    case 'E' : return(QRectF(85,43,20,20));
    case 'F' : return(QRectF(106,43,20,20));
    case 'G' : return(QRectF(127,43,20,20));
    case 'H' : return(QRectF(148,43,20,20));
    case 'I' : return(QRectF(169,43,20,20));
    case 'J' : return(QRectF(190,43,20,20));
    case 'K' : return(QRectF(211,43,20,20));
    case 'L' : return(QRectF(232,43,20,20));
    case 'M' : return(QRectF(253,43,20,20));
    case 'N' : return(QRectF(274,43,20,20));
    case 'O' : return(QRectF(295,43,20,20));
    case 'P' : return(QRectF(316,43,20,20));
    case 'Q' : return(QRectF(1,64,20,20));
    case 'R' : return(QRectF(22,64,20,20));
    case 'S' : return(QRectF(43,64,20,20));
    case 'T' : return(QRectF(64,64,20,20));
    case 'U' : return(QRectF(85,64,20,20));
    case 'V' : return(QRectF(106,64,20,20));
    case 'W' : return(QRectF(127,64,20,20));
    case 'X' : return(QRectF(148,64,20,20));
    case 'Y' : return(QRectF(169,64,20,20));
    case 'Z' : return(QRectF(190,64,20,20));
    case '[' : return(QRectF(211,64,20,20));
    case '\\' : return(QRectF(232,64,20,20));
    case ']' : return(QRectF(253,64,20,20));
    case '^' : return(QRectF(274,64,20,20));
    case '_' : return(QRectF(295,64,20,20));
    case '`' : return(QRectF(316,64,20,20));
    case 'a' : return(QRectF(1,85,20,20));
    case 'b' : return(QRectF(22,85,20,20));
    case 'c' : return(QRectF(43,85,20,20));
    case 'd' : return(QRectF(64,85,20,20));
    case 'e' : return(QRectF(85,85,20,20));
    case 'f' : return(QRectF(106,85,20,20));
    case 'g' : return(QRectF(127,85,20,20));
    case 'h' : return(QRectF(148,85,20,20));
    case 'i' : return(QRectF(169,85,20,20));
    case 'j' : return(QRectF(190,85,20,20));
    case 'k' : return(QRectF(211,85,20,20));
    case 'l' : return(QRectF(232,85,20,20));
    case 'm' : return(QRectF(253,85,20,20));
    case 'n' : return(QRectF(274,85,20,20));
    case 'o' : return(QRectF(295,85,20,20));
    case 'p' : return(QRectF(316,85,20,20));
    case 'q' : return(QRectF(1,106,20,20));
    case 'r' : return(QRectF(22,106,20,20));
    case 's' : return(QRectF(43,106,20,20));
    case 't' : return(QRectF(64,106,20,20));
    case 'u' : return(QRectF(85,106,20,20));
    case 'v' : return(QRectF(106,106,20,20));
    case 'w' : return(QRectF(127,106,20,20));
    case 'x' : return(QRectF(148,106,20,20));
    case 'y' : return(QRectF(169,106,20,20));
    case 'z' : return(QRectF(190,106,20,20));
    case '{' : return(QRectF(211,106,20,20));
    case '|' : return(QRectF(232,106,20,20));
    case '}' : return(QRectF(253,106,20,20));
    case '~' : return(QRectF(274,106,20,20));
    default : return(QRectF(22,1,20,20));

}


}

//this is the actual map
class SlippyMap: public QObject
{
    Q_OBJECT

public:
    int width;
    int height;
    int zoom;
    qreal latitude;
    qreal longitude;
    int clearTime;
    QHash<QPoint, QPixmap> m_tilePixmaps;
    QList<activePacket> plotList;
    QList<QString> plotListId;



    SlippyMap(QObject *parent = 0)
        : QObject(parent)
        , width(800)
        , height(450)
        , zoom(7)
        , latitude(34.722940)
        , longitude(-86.640006) {
        m_emptyTile = QPixmap(tdim, tdim);
        m_emptyTile.fill(Qt::lightGray);



        clearTime = 60; //time to clear a packet off map in seconds
        debugDrawing=false;

        QNetworkDiskCache *cache = new QNetworkDiskCache;
        cache->setCacheDirectory(QDesktopServices::storageLocation
                                 (QDesktopServices::CacheLocation));
        m_manager.setCache(cache);
        connect(&m_manager, SIGNAL(finished(QNetworkReply*)),
                this, SLOT(handleNetworkData(QNetworkReply*)));
    }

    void invalidate() {
        if (width <= 0 || height <= 0)
            return;

        QPointF ct = tileForCoordinate(latitude, longitude, zoom);
        qreal tx = ct.x();
        qreal ty = ct.y();

        // top-left corner of the center tile
        int xp = width / 2 - (tx - floor(tx)) * tdim;
        int yp = height / 2 - (ty - floor(ty)) * tdim;

        // first tile vertical and horizontal
        int xa = (xp + tdim - 1) / tdim;
        int ya = (yp + tdim - 1) / tdim;
        int xs = static_cast<int>(tx) - xa;
        int ys = static_cast<int>(ty) - ya;

        // offset for top-left tile
        m_offset = QPoint(xp - xa * tdim, yp - ya * tdim);

        // last tile vertical and horizontal
        int xe = static_cast<int>(tx) + (width - xp - 1) / tdim;
        int ye = static_cast<int>(ty) + (height - yp - 1) / tdim;

        // build a rect
        m_tilesRect = QRect(xs, ys, xe - xs + 1, ye - ys + 1);

        if (m_url.isEmpty())
            download();

        emit updated(QRect(0, 0, width, height));
    }

    void render(QPainter *p, const QRect &rect) {
        for (int x = 0; x <= m_tilesRect.width(); ++x)
            for (int y = 0; y <= m_tilesRect.height(); ++y) {
            QPoint tp(x + m_tilesRect.left(), y + m_tilesRect.top());
            QRect box = tileRect(tp);
            if (rect.intersects(box)) {
                if (m_tilePixmaps.contains(tp))
                    p->drawPixmap(box, m_tilePixmaps.value(tp));
                else
                    p->drawPixmap(box, m_emptyTile);

            }
        }//redraw map

        plot(p);

    }

    void plot(QPainter *p){

        //replot symbols and drawings on map everytime map moves

        killTimer(timerID); //stops timer from last funciton call
        /*
        QSqlQuery query;
        query.exec("SELECT * FROM PACKETS");
*/
        QPixmap pixmap;
        QString symbolpath = ":mysymb.GIF";
        pixmap.load(symbolpath);
        QPointF lastPos;
        QString lastPlotName ="";


        for(int i=plotList.size()-1;i>=0;i--){ //we move through the list backwards, since newer call signs are at the end



            QPointF symbolPos =    (tileForCoordinate(plotList[i].latitude,plotList[i].longitude,zoom)
                                    -tileForCoordinate(latitude,longitude,zoom))
                                   *qreal(tdim) +QPoint(width/2,height/2);
            plotList[i].symbolPos=QPointF(-11,-11);

            if(!(symbolPos.x()>0&&symbolPos.y()>0&&symbolPos.x()<width&&symbolPos.y()<height)){

                lastPos=symbolPos;
                lastPlotName=plotListId[i];
                //if(debugDrawing==true)
                // qDebug()<<i<< plotListId[i]<< "list"<<plotList[i].plotName<<plotList[i].timestamp.toString("hh:mm:ss") <<"off map";

                continue;

            }

            if(plotListId[i]==lastPlotName){ //if previous packet was not most recent for callsign
                p->setPen(plotList[i].lineColor);
                p->drawLine(lastPos,symbolPos);

                lastPlotName = plotListId[i];
                if(debugDrawing==true)
                    qDebug()<<i <<plotListId[i]<< "list"<<plotList[i].plotName<<plotList[i].timestamp.toString("hh:mm:ss") <<"line seg " <<symbolPos <<"from"<<lastPos;
                lastPos=symbolPos;
                continue;
            }
            p->setBackgroundMode(Qt::TransparentMode);
            p->drawPixmap(QRectF(symbolPos-QPointF(10,10),QSizeF(20,20)),pixmap,plotList[i].symbolGridPos);
            plotList[i].symbolPos=symbolPos;
            if(zoom>10)//draws the call sign at high zoom levels
                p->drawText(symbolPos-QPoint(10,5),plotList[i].plotName);

            lastPlotName=plotListId[i];
            if(debugDrawing==true)
                qDebug()<<i <<plotList[i].plotName<< "data"<<plotList[i].data<<plotList[i].timestamp.toString("hh:mm:ss") <<"symbol at" <<symbolPos;
            lastPos=symbolPos;

        }




        debugDrawing=false;
        timerID=startTimer(500); //function will rerun in 0.5s if not called sooner

    }

    void printDrawingDebug(){

        for(int i=plotListId.size()-1;i>=0;i--)
            qDebug()<<i <<plotListId[i]<< "list"<<plotList[i].source<<plotList[i].timestamp.toString("hh:mm:ss") << plotList[i].latitude << plotList[i].longitude ;
        debugDrawing=true;


    }

    void clearMap(){

        plotListId.clear();
        plotList.clear();
    }

    QString getTooltipString(QPoint mousePos){

        for(int i=0;i<plotListId.size();i++)
            if(abs(plotList[i].symbolPos.x()-mousePos.x())<10)
                if(abs(plotList[i].symbolPos.y()-mousePos.y())<10)
                    return QString("%1\n Source: %2\nDestination: %3\nTimestamp: %4\nMessage: %5\nType: %6").arg(plotList[i].plotName).arg(plotList[i].source).arg(plotList[i].dest).arg(plotList[i].timestamp.toString("hh.mm.ss")).arg(plotList[i].message).arg(plotList[i].type);
        return QString();
    }

    void cleanPlotList(){


        for(int i=0; i<plotList.size();i++)
            if(QDateTime::currentDateTime().operator >(plotList[i].timestamp.addSecs(clearTime))){
            plotListId.removeAt(i);
            plotList.removeAt(i);
        }
        //qDebug()<<"size now:"<<plotList.;



    }

    void pan(const QPoint &delta) {
        QPointF dx = QPointF(delta) / qreal(tdim);
        QPointF center = tileForCoordinate(latitude, longitude, zoom) - dx;

        latitude = latitudeFromTile(center.y(), zoom);
        longitude = longitudeFromTile(center.x(), zoom);
        invalidate();

    }

private slots:

    void handleNetworkData(QNetworkReply *reply) {
        /*This function is called automatically when the program receives a network reply after
          requesting a file. The function stores the file in the program cache but also stores a copy locally*/

        QImage img;
        QPoint tp = reply->request().attribute(QNetworkRequest::User).toPoint();
        QUrl url = reply->url();
        if (!reply->error()){
            if (!img.load(reply, 0))
                img = QImage();

        }
        m_tilePixmaps[tp] = QPixmap::fromImage(img);

        //save file

        QString filename ="../maps/";
        filename.append(url.toString().remove("http://tile.openstreetmap.org/",Qt::CaseInsensitive));
        // zoom / x / y . png
        QString filepath = filename;
        filepath.truncate(filepath.lastIndexOf('/'));
        if(!QDir(filepath).exists(filename.remove(filepath))){
            QDir().mkpath(filepath);
            QPixmap(m_tilePixmaps.value(tp)).save(filename,0,-1);

            reply->deleteLater();

        }


        if (img.isNull())
            m_tilePixmaps[tp] = m_emptyTile;
        emit updated(tileRect(tp));

        // purge unused spaces
        QRect bound = m_tilesRect.adjusted(-2, -2, 2, 2);
        foreach(QPoint tp, m_tilePixmaps.keys())
            if (!bound.contains(tp))
                m_tilePixmaps.remove(tp);

        download();
    }


    void download() {


        /*This function checks to see if the required tiles are in the cache.
          If not in the cache, it will check the local folder. If the file was unable to load from folder
          the function will attempt to download from openstreetmaps.org  */

        int lockzoom=zoom;
        QPoint grab(0, 0);  //tile to grab
        for (int x = 0; x <= m_tilesRect.width(); ++x)
            for (int y = 0; y <= m_tilesRect.height(); ++y) { // cycle through tiles
            QPoint tp = m_tilesRect.topLeft() + QPoint(x, y);
            if (!m_tilePixmaps.contains(tp)) { //if program cache does not contain tile


                QString filepath="../maps/%1/%2/%3.png";
                QImage file;
                if (file.load(filepath.arg(lockzoom).arg(tp.x()).arg(tp.y()))){ //check local folder for image
                    m_tilePixmaps[tp] = QPixmap::fromImage(file);
                    continue;
                }
                else{
                    grab = tp;
                    break;
                }
            }
        }

        if(grab==QPoint(0,0)){
            m_url=QUrl();
            return;
        }


        QString path = "http://tile.openstreetmap.org/%1/%2/%3.png";
        m_url = QUrl(path.arg(lockzoom).arg(grab.x()).arg(grab.y()));
        QNetworkRequest request;
        request.setUrl(m_url);
        request.setRawHeader("User-Agent", "Nokia (Qt) Graphics Dojo 1.0");
        request.setAttribute(QNetworkRequest::User, QVariant(grab));
        m_manager.get(request);



    }

    void newPacket(APRSParser parse){

        //adds new packets to the QList and it's index helper. If the call sign is already in the
        //QList, this will add it after the last occurance of the call sign.

        if(parse.latitude!=parse.latitude)//if no latitude for station, don't add to plot list
            return;
        activePacket packet;
        packet.latitude=parse.latitude;
        packet.longitude=parse.longitude;
        packet.dest=parse.dstcallsign;
        packet.source=parse.srccallsign;
        packet.data=parse.data;
        packet.type=parse.type;
        packet.symbolGridPos=getSymbol(parse.symbolcode);
        packet.timestamp=QDateTime::currentDateTime();
        if(packet.type==T_OBJECT||packet.type==T_ITEM)
            packet.plotName=packet.data;
        else
            packet.plotName=packet.source;

        int i = plotListId.lastIndexOf(packet.plotName);

        if(i==-1){
            packet.lineColor=QColor((parse.second*10)%255,(parse.second*20)%255,(parse.second*5)%255);
            plotListId.append(packet.plotName);
            plotList.append(packet);
        }
        else{
            packet.lineColor=plotList[i].lineColor;
            plotListId.insert(i+1,packet.plotName);
            plotList.insert(i+1,packet);
        }

    }

signals:

    void updated(const QRect &rect);

protected:
    QRect tileRect(const QPoint &tp){
        QPoint t = tp - m_tilesRect.topLeft();
        int x = t.x() * tdim + m_offset.x();
        int y = t.y() * tdim + m_offset.y();
        return QRect(x, y, tdim, tdim);
    }

    void timerEvent(QTimerEvent *event)
    {
        killTimer(event->timerId());
        cleanPlotList();
        emit updated(QRect(0, 0, width, height));
        //invalidate();
    }

private:
    QPoint m_offset;
    QRect m_tilesRect;
    QPixmap m_emptyTile;

    // QSqlDatabase packetsDB;
    QNetworkAccessManager m_manager;
    QUrl m_url;
    int timerID;
    int downloadedX, downloadedY, downloadedZoom;
    bool debugDrawing;

};

//this is the widget that contains map
class LightMaps: public QWidget
{
    Q_OBJECT

public:
    SlippyMap *m_normalMap;

    LightMaps(QWidget *parent = 0)
        : QWidget(parent)
        , pressed(false)
        , snapped(false)
        , zoomed(false) {

        setMouseTracking (true);

        m_normalMap = new SlippyMap(this);
        connect(m_normalMap, SIGNAL(updated(QRect)), SLOT(updateMap(QRect)));


    }

    void setCenter(qreal lat, qreal lng) {
        m_normalMap->latitude = lat;
        m_normalMap->longitude = lng;
        m_normalMap->invalidate();

    }

            private slots:
    void updateMap(const QRect &r) {
        update(r);
    }

            protected:

    void paintEvent(QPaintEvent *event) {
        QPainter p;
        p.begin(this);
        m_normalMap->render(&p, event->rect());
        p.setPen(Qt::black);

        p.drawText(rect(),  Qt::AlignBottom | Qt::TextWordWrap,
                   "Map data CCBYSA 2011 OpenStreetMap.org contributors");
        p.end();

    }

    void mousePressEvent(QMouseEvent *event) {
        if (event->buttons() == Qt::LeftButton){
            pressed = snapped = true;
            pressPos = dragPos = event->pos();
        }
        if (event->buttons()==Qt::RightButton){

            QPoint mousePos = mapFromGlobal(QCursor::pos());
            for(int i=0;i<m_normalMap->plotListId.size();i++)
                if(abs(m_normalMap->plotList[i].symbolPos.x()-mousePos.x())<10)
                    if(abs(m_normalMap->plotList[i].symbolPos.y()-mousePos.y())<10){
                emit rightClickPopUp(m_normalMap->plotList[i].source);
            }
        }
    }
    void resizeEvent(QResizeEvent *event){


        m_normalMap->width = event->size().width();
        m_normalMap->height = event->size().height();
    }

    void mouseMoveEvent(QMouseEvent *event) {
        if (!event->buttons())
        {
            QToolTip::hideText ();
            killTimer(tooltipTimer);
            tooltipTimer=startTimer(1000);
            update();
            return;
        }

        if (!pressed || !snapped) {
            QPoint delta = event->pos() - pressPos;
            pressPos = event->pos();
            m_normalMap->pan(delta);
            update();
            return;
        } else {
            const int threshold = 10;
            QPoint delta = event->pos() - pressPos;
            if (snapped) {
                snapped &= delta.x() < threshold;
                snapped &= delta.y() < threshold;
                snapped &= delta.x() > -threshold;
                snapped &= delta.y() > -threshold;
            }
            if (!snapped)
                tapTimer.stop();
            update();

        }

    }

    void timerEvent(QTimerEvent *event){
        if(event->timerId()==tooltipTimer){
            killTimer(tooltipTimer);
            QString s= this->m_normalMap->getTooltipString(mapFromGlobal(QCursor::pos()));
            QToolTip::showText ( QCursor::pos(),s,this);

        }
    }

    void mouseDoubleClickEvent ( QMouseEvent * event ) {

        m_normalMap->pan(-event->pos()+QPoint(m_normalMap->width/2,m_normalMap->height/2));
        if(m_normalMap->zoom>14)
            m_normalMap->zoom = 17;
        else
            m_normalMap->zoom = m_normalMap->zoom+3;
        m_normalMap->m_tilePixmaps.clear();
        m_normalMap->invalidate();

    }

    void mouseReleaseEvent(QMouseEvent *) {
        update();
    }

    void wheelEvent(QWheelEvent *event){

        wheelDelta += event->delta();

        if (wheelDelta >=240 && m_normalMap->zoom<17){
            m_normalMap->zoom++;
            wheelDelta=wheelDelta%240;
            m_normalMap->m_tilePixmaps.clear();
            m_normalMap->invalidate();
            emit wheelZoomed(m_normalMap->zoom);
        }
        else if(wheelDelta <= -240 && m_normalMap->zoom>3){
            m_normalMap->zoom--;
            wheelDelta = wheelDelta%240;
            m_normalMap->m_tilePixmaps.clear();
            m_normalMap->invalidate();
            emit wheelZoomed(m_normalMap->zoom);
        }

        update();

    } //zoom in/zoom out with mouse scroll

    void keyPressEvent(QKeyEvent *event) {

        if (event->key() == Qt::Key_Left)
            m_normalMap->pan(QPoint(20, 0));
        if (event->key() == Qt::Key_Right)
            m_normalMap->pan(QPoint(-20, 0));
        if (event->key() == Qt::Key_Up)
            m_normalMap->pan(QPoint(0, 20));
        if (event->key() == Qt::Key_Down)
            m_normalMap->pan(QPoint(0, -20));
        if (event->key() == Qt::Key_Z || event->key() == Qt::Key_Select) {
            dragPos = QPoint(width() / 2, height() / 2);
        }
    }

    void resizeEvent () {

        m_normalMap->height= size().height();
        m_normalMap->width = size().width();

    }

            private:

    bool pressed;
    bool snapped;
    int wheelDelta;
    QPoint pressPos;
    QPoint dragPos;
    QBasicTimer tapTimer;
    bool zoomed;
    QPixmap zoomPixmap;
    QPixmap maskPixmap;
    int tooltipTimer;


            signals:
    void rightClickPopUp(QString);
    void wheelZoomed(const int &zoom);
};

//main window

class MapZoom : public QMainWindow
{
    Q_OBJECT


public:
    QSqlDatabase packetsDatabase;
    QList<ConnectionThread*> connectionList;
    QList<QString> connectionNameList;
    bool useMySQL;

private:
    LightMaps *map;
    DockWidget *sideDock;
    ConnectionManager *connectionManager;
    //ConnectStatus *bottomDock;
    configStation *stationConfig;

    TimingSettings *timingSettings;
    PurgeThread *purger;
/*
    void replay(QDateTime startTime, QSqlQuery query){
        //this function is under contrstruction
        query.exec("SELECT * FROM packets;");

        while (query.next())
        {
            if (startTime >  QDateTime::fromString(query.value(1).toString(),"Mddyyyyhhmmsszzz")){
                APRSParser a;
                a.parse(query.value(9).toString());
                emit newPacket(a);
            }

        }
    }
*/
            public:

    MapZoom(): QMainWindow(0) {
        map = new LightMaps(this);
        sideDock = new DockWidget::DockWidget(this);
        setCentralWidget(map);
        //sideDock->hide();
        bool useMySQL = false;
        stationConfig = new configStation::configStation(this);
        setupDb(useMySQL);
        addDockWidget(Qt::RightDockWidgetArea, sideDock);
        //bottomDock = new ConnectStatus::ConnectStatus(this);
        //addDockWidget(Qt::BottomDockWidgetArea, bottomDock);
        map->setFocus();
        map->m_normalMap->invalidate();
        connectionManager = new ConnectionManager::ConnectionManager(this);
        timingSettings = new TimingSettings::TimingSettings(this);
        purger = new PurgeThread(packetsDatabase, timingSettings->timingDatabase);




        purger->start();


        connect(this->sideDock->ui->horizontalSlider,SIGNAL(valueChanged(int)),this, SLOT(updateZoom()));

        connect(this->map,SIGNAL(wheelZoomed(int)),sideDock->ui->horizontalSlider,SLOT(setValue(int)));
        //connect(this->bottomDock,SIGNAL(connectPushed()),this,SLOT(connectButtonPushed()));
        //connect(this->bottomDock,SIGNAL(disconnectPushed()),this,SLOT(disconnectButtonPushed()));

        connect(this->sideDock,SIGNAL(connectPushed(QString)),this,SLOT(conMgrConnect(QString)));
        connect(this->sideDock,SIGNAL(disconnectPushed(QString)),this,SLOT(conMgrDisconnect(QString)));
        connect(this->connectionManager,SIGNAL(connectPushed(QString)),this,SLOT(conMgrConnect(QString)));
        connect(this->connectionManager,SIGNAL(disconnectPushed(QString)),this,SLOT(conMgrDisconnect(QString)));
        connect(this->connectionManager,SIGNAL(UpdateSideDock()), this, SLOT(UpdateSideDockList()));
        connect(this->map,SIGNAL(rightClickPopUp(QString)),this,SLOT(doRightClickPopUp(QString)));
        connect(this->timingSettings,SIGNAL(updateClearTime(int)),this,SLOT(updateClearTime(int)));


        //menu
        {
            QAction *osloAction = new QAction("&Oslo", this);
            QAction *berlinAction = new QAction("&Berlin", this);
            QAction *jakartaAction = new QAction("&Jakarta", this);
            QAction *showOptions = new QAction("Show Side Pane",this);
            QAction *osmAction = new QAction("About OpenStreetMap", this);
            QAction *showClient = new QAction ("Show APRS Client",this);
            QAction *exitProgram = new QAction("Exit", this);
            QAction *configure = new QAction("Configure Station",this);
            QAction *clearMap = new QAction("Clear Map",this);
            QAction *debugDrawing = new QAction ("print drawing debug",this);
            QAction *manageConnections = new QAction("Manage Connections", this);
            QAction *timing = new QAction("Timing Settings", this);
            QAction *showTable = new QAction("View Packet Database",this);

            connect(showTable, SIGNAL(triggered()), SLOT(showPacketTable()));
            connect(debugDrawing, SIGNAL(triggered()), SLOT(debugMapDrawing()));
            connect(clearMap, SIGNAL(triggered()), SLOT(callClearMap()));
            connect(configure, SIGNAL(triggered()),SLOT(showStationConfig()));
            connect(exitProgram, SIGNAL(triggered()), SLOT(exitProgram()));
            connect(showClient, SIGNAL(triggered()),SLOT(showClient()));
            connect(osloAction, SIGNAL(triggered()), SLOT(chooseOslo()));
            connect(berlinAction, SIGNAL(triggered()), SLOT(chooseBerlin()));
            connect(jakartaAction, SIGNAL(triggered()), SLOT(chooseJakarta()));
            connect(osmAction, SIGNAL(triggered()), SLOT(aboutOsm()));
            connect(showOptions, SIGNAL(triggered()),SLOT(showOptions()));
            connect(manageConnections, SIGNAL(triggered()), SLOT(manageConnections()));
            connect(timing, SIGNAL(triggered()), SLOT(showTimingSettings()));
            connect(timingSettings, SIGNAL(SendPositionReport()), SLOT(FirePositionReport()));


            QMenu *fileMenu = menuBar()->addMenu("&File");
            QMenu *viewMenu = menuBar()->addMenu("&View");
            QMenu *setMenu = menuBar()->addMenu("&Settings");
            QMenu *optMenu = menuBar()->addMenu("&Options");
            QMenu *helpMenu = menuBar()->addMenu("&Help");
            QMenu *debugMenu = menuBar()->addMenu("&Debug");

            debugMenu->addAction(clearMap);
            //debugMenu->addAction(debugDrawing);
            setMenu->addAction(configure);
            fileMenu->addAction(exitProgram);
            //viewMenu->addAction(showClient);
            viewMenu->addAction(showTable);
            viewMenu->addAction(showOptions);
            setMenu->addAction(manageConnections);
            setMenu->addAction(timing);

            optMenu->addAction(osloAction);
            optMenu->addAction(berlinAction);
            optMenu->addAction(jakartaAction);
            setMenu->addSeparator();
            helpMenu->addAction(osmAction);


        }

        QTimer::singleShot(0, this, SLOT(delayedInit()));
    }


    void startNewConnection(QString server, int port, QString username, QString password, QString filter,QString conName){

        ConnectionThread *thread;
        thread = new ConnectionThread::ConnectionThread(server, port, username, password, filter);
        connect(thread,SIGNAL(newPacket(APRSParser)),map->m_normalMap,SLOT(newPacket(APRSParser)));
        thread->start();



        connectionNameList.append(conName);
        connectionList.append(thread);
    }

    void setupDb(bool &useMySQL){
/*
        if(useMySQL==true){
            packetsDatabase = QSqlDatabase::addDatabase("QMYSQL");
            packetsDatabase.setHostName("pavelow.eng.uah.edu");
            packetsDatabase.setPort(33001);
            packetsDatabase.setUserName("aprs");
            packetsDatabase.setPassword("password");
            packetsDatabase.setDatabaseName("db");
        }
        else {
            packetsDatabase = QSqlDatabase::addDatabase("QSQLITE");
            packetsDatabase.setDatabaseName("../packets_database.db");
        }


        // MAKE SURE DATABASE OPENS SUCCESSFULLY
        if (!packetsDatabase.open())
        {
            qDebug() << "Database Error :: lightmaps.cpp setubDb function";
            qDebug() << packetsDatabase.lastError();
        }


        populatePacketsDatabase(packetsDatabase); // populate packetsDatabase

        QSqlQuery q(packetsDatabase);
        q.exec("select * from packets");
*/


        QSqlQuery q(this->stationConfig->settingsDatabase);
        if(!(q.exec("select usemysql from settings")))
            qDebug()<<q.lastError()<<"error checking usemysql check";

        q.next();
        if(q.value(0)==1)
            useMySQL=true;
        else
            useMySQL=false;
        qDebug()<<"useMySql var:"<<useMySQL;

        if(useMySQL==true){
            QSqlQuery q2(this->stationConfig->settingsDatabase);
            if(!(q2.exec("select server, port, dbname, username, password from settings")))
                  qDebug()<<q2.lastError()<<"error getting mysql settings from settings db";
            q2.next();
            packetsDatabase = QSqlDatabase::addDatabase("QMYSQL");
            packetsDatabase.setHostName(q2.value(0).toString());
            packetsDatabase.setPort(q2.value(1).toInt());
            packetsDatabase.setUserName(q2.value(3).toString());
            packetsDatabase.setPassword(q2.value(4).toString());
            packetsDatabase.setDatabaseName(q2.value(2).toString());
            qDebug()<<"set the mySqlDatabase to: "<<packetsDatabase.hostName() <<packetsDatabase.databaseName();
        }
        else {
            packetsDatabase = QSqlDatabase::addDatabase("QSQLITE");
            packetsDatabase.setDatabaseName("../packets_database.db");
        }

        // MAKE SURE DATABASE OPENS SUCCESSFULLY
        if (!packetsDatabase.open())
        {
            qDebug() << "Database Error :: lightmaps.cpp setubDb function";
            qDebug() << packetsDatabase.lastError();
        }

        populatePacketsDatabase(packetsDatabase); // populate packetsDatabase

        QSqlQuery q3(packetsDatabase);
        q3.exec("select * from packets");

    }


    ~MapZoom(){
       // purger.quit();
        //packetsDatabase.close();
    }


            signals:

    void newPacket(APRSParser);

            private slots:

    void updateClearTime(int t){
        map->m_normalMap->clearTime = t*60;

    }

    void doRightClickPopUp(QString str){

        QSqlQuery query(packetsDatabase);
        query.prepare("select * from PACKETS where source_call_sign = ?");
        query.addBindValue(str);
        query.exec();
        query.next();
        QSqlQueryModel *results = new QSqlQueryModel;
        results->setQuery(query);
        QTableView* rightClickView = new QTableView;
        rightClickView->setModel(results);
        rightClickView->setWindowTitle(str);
        rightClickView->setMinimumWidth(600);
        rightClickView->show();

    }

    void UpdateSideDockList()
    {
        sideDock->RetrieveAllConnectionNames();
    }

    void FirePositionReport(){
        QByteArray FireString;
        QSqlQuery query(stationConfig->settingsDatabase);
        query.prepare("select * from settings");
        query.exec();
        while(query.next())
        {
            FireString.append(query.value(0).toString());
            FireString.append(">APUAH1:!");

            if(query.value(4).toString()=="1")
            {
                if (query.value(5).toString() > "0")
                {
                    FireString.append(query.value(5).toString());
                    //qDebug() << query.value(5).toInt() << "lat is already positive";
                    FireString.append("N/");
                }

                else //(query.value(5).toString() < "0");
                {
                    QString s = query.value(5).toString();
                    s.remove(0,1);
                    //qDebug() << s << "long is now positive";
                    FireString.append(s);
                    FireString.append("S/");
                }

                if (query.value(6).toString() > "0")
                {
                    FireString.append(query.value(6).toString());
                    FireString.append("E");
                    //qDebug() << "long is already positive";
                }
                else
                {
                    QString r = query.value(6).toString();
                    r.remove(0,1);
                    FireString.append(r);
                    FireString.append("W");
                    //qDebug() << "long is now positive";
                }
            }
            else // (query.value(4).toString()=="0")
            {
                QString a;
                QString b;

                if (query.value(7).toString() > "0")
                {
                    FireString.append(query.value(7).toString());
                    a = "N/";
                    //  qDebug() << "lat deg are pos";
                }
                else
                {
                    QString s = query.value(7).toString();
                    s.remove(0,1);
                    FireString.append(s);
                    a = "S/";
                    //   qDebug() << "lat deg are now pos";
                }
                float w = query.value(8).toInt();
                float x = query.value(9).toInt();
                w = w/60;
                x = x/3600;
                //qDebug() << w << "<<<that's w before chop and that's x>>>" << x;
                QString l = QString::number(w, 'f', 2);
                QString m = QString::number(x, 'f', 2);
                l.remove(0,2);
                m.remove(0,2);
                //qDebug() << l << "<--that's minutes and that's seconds -->" << m;
                FireString.append(l);
                FireString.append(".");
                FireString.append(m);
                FireString.append(a);
                //  qDebug() << FireString;

                if ((query.value(10).toInt() > -100) && (query.value(10).toInt() < 100))
                    FireString.append("0");
                if (query.value(10).toString() > "0")
                {
                    FireString.append(query.value(10).toString());
                    b = "E";
                    //   qDebug() << "long deg are already positive";
                }
                else
                {
                    QString p = query.value(10).toString();
                    p.remove(0,1);
                    // qDebug() << p << "long is now positive";
                    FireString.append(p);
                    b = "W";
                }

                float y = query.value(11).toInt();
                float z = query.value(12).toInt();
                y = y/60;
                z = z/3600;
                QString n = QString::number(y, 'f', 2);
                QString o = QString::number(z, 'f', 2);
                n.remove(0,2);
                o.remove(0,2);
                FireString.append(n);
                FireString.append(".");
                FireString.append(o);
                FireString.append(b);
            }


            FireString.append(query.value(1).toString());
            FireString.append(query.value(3).toString());
            FireString.append("\n");

            qDebug() << FireString << "complete string!!!!";
        }


        for(int i = 0; i < connectionList.length(); i++)
        {
            // qDebug() << "position report sent";
            //qDebug() << FireString;
            connectionList[i]->connection->write(FireString);
            //connectionList[i]->connection->write("UAH01>APK001,W4GPS-7,WIDE1*,WIDE,2-1,qAR,N8DEU-5:$GPRMC,083340,A,3459.2000,N,08643.6714,W,0.0,336.1,170411,2.5,W,A*10\n");
        }
    }

    void replayoption(){
        //under construction
        // APRSParser

    }

    void conMgrConnect(QString conName){

        QSqlQuery query(connectionManager->connectionsDatabase);
        query.prepare("select * from connections where connection_name = ?");
        query.addBindValue(conName);
        query.exec();
        qDebug()<<query.lastError();
        query.next();
        startNewConnection(query.value(1).toString(),query.value(2).toInt(),query.value(3).toString(),query.value(4).toString(),query.value(5).toString(),query.value(0).toString());
        //connectionNameList.append(query.value(0).toString());

    }

    void conMgrDisconnect(QString conName){

        int i=connectionNameList.indexOf(conName);
        if(i<0)
            return;
        connectionList[i]->removeConnection();
        connectionNameList.removeAt(i);
        connectionList.removeAt(i);

    }

    void disconnectButtonPushed(){

        for(int i=0;i<connectionList.length();i++){
            connectionList[i]->removeConnection();


            connectionList.removeAt(i);
            connectionNameList.removeAt(i);
        }

    }

    void connectButtonPushed(){

        QSqlQuery query(connectionManager->connectionsDatabase);
        query.exec("select * from connections");
        while(query.next()){

            startNewConnection(query.value(1).toString(),query.value(2).toInt(),query.value(3).toString(),query.value(4).toString(),query.value(5).toString(),query.value(0).toString());
        }

    }

    void showPacketTable(){

        QSqlQueryModel *testQuery = new QSqlQueryModel;
        testQuery->setQuery("SELECT * FROM packets;");
        QTableView *test = new QTableView;
        test->setModel(testQuery);
        test->setWindowTitle("Packets Database");
        test->show();
    }

    void debugMapDrawing(){
        this->map->m_normalMap->printDrawingDebug();
    }

    void callClearMap(){

        qDebug()<<this->packetsDatabase.isOpen()<<"is open";
        qDebug()<<this->packetsDatabase.isOpenError()<<"is open error";
        qDebug()<<this->packetsDatabase.lastError() <<"last error";
         qDebug()<<this->packetsDatabase.isValid()<<"is valid";
        this->map->m_normalMap->clearMap();
    }

    void delayedInit() {

    }

    void showTimingSettings()
    {
        timingSettings->show();
    }

    void updateZoom() {

        map->m_normalMap->zoom = this->sideDock->ui->horizontalSlider->value();
        qDebug() <<"map->m_normalMap->zoom now "<<map->m_normalMap->zoom;
        map->m_normalMap->invalidate();

    }

    void gotoUserCoords(){

        //map->setCenter(this->sideDock->ui->longLineEdit->text().toFloat(),this->sideDock->ui->latLineEdit->text().toFloat());
    }
    void showOptions() {

        sideDock->show();
    }

    void exitProgram(){
        this->close();
    }


    void showClient(){
        //    client->show();
    }

    void manageConnections(){
        connectionManager->show();
    }

    void showStationConfig(){
        stationConfig->show();
    }
    void chooseOslo() {
        map->setCenter(59.9138204, 10.7387413);
    }

    void chooseBerlin() {
        map->setCenter(52.52958999943302, 13.383053541183472);
    }

    void chooseJakarta() {
        map->setCenter(-6.211544, 106.845172);
    }

    void aboutOsm() {
        QDesktopServices::openUrl(QUrl("http://www.openstreetmap.org"));
    }
};

#include "lightmaps.moc"

int main(int argc, char **argv)
{
#if defined(Q_WS_X11)
    QApplication::setGraphicsSystem("raster");
#endif

    QApplication app(argc, argv);
    app.setApplicationName("4loco-aprs");
    MapZoom w;
    w.setWindowTitle("4loco-aprs");

    w.resize(800, 450);
    w.show();



    return app.exec();
}
